package com.gitee.wsl.net.client

import com.gitee.wsl.ext.base.ifTrue
import com.gitee.wsl.io.File
import com.gitee.wsl.ext.array.toByteString
import com.gitee.wsl.io.writePacket
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.content.ProgressListener
import io.ktor.client.plugins.onDownload
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.head
import io.ktor.client.request.header
import io.ktor.client.request.headers
import io.ktor.client.request.options
import io.ktor.client.request.patch
import io.ktor.client.request.post
import io.ktor.client.request.prepareGet
import io.ktor.client.request.put
import io.ktor.client.request.request
import io.ktor.client.request.setBody
import io.ktor.client.request.url
import io.ktor.client.statement.HttpResponse

import io.ktor.client.statement.HttpStatement
import io.ktor.client.statement.bodyAsBytes
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentLength
import io.ktor.http.contentType
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.readRemaining
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.io.bytestring.ByteString
import timber.log.Timber
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.min


inline fun <reified T> HttpClient.requestFlow(
    builder: HttpRequestBuilder = HttpRequestBuilder()
) = flow {
    HttpStatement(builder = builder, client = this@requestFlow)
        .execute()
        .body<T>()
        .also { response -> emit(value = response) }
}

/**
 * override fun getMoviesByCategory(
 *         category: String,
 *         page: Int,
 *         language: String?,
 *         region: String?,
 *     ): Flow<PaginatedData<Movie>> = client.getFlow<MoviesPaginatedScheme> {
 *         url(scheme = "https", host = TmdbConfig.HOST, path = "/3/movie/$category") {
 *             parameter(key = "api_key", value = TmdbConfig.API_KEY)
 *             parameter(key = "page", value = page)
 *             parameter(key = "language", value = language)
 *             parameter(key = "region", value = region)
 *         }
 *     }.map(MoviesPaginatedScheme::toDomain)
 */

 inline fun <reified T> HttpClient.getFlow(builder: HttpRequestBuilder) =
    requestFlow<T>(builder = builder.apply { method = HttpMethod.Get })

 inline fun <reified T> HttpClient.getFlow(
    block: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = getFlow(builder = HttpRequestBuilder().apply(block = block))


suspend inline fun <reified T> HttpClient.postJson(
           urlString: String,
           bean:T
        ): HttpResponse = post {
               url(urlString)
               contentType(ContentType.Application.Json)
               setBody(bean)
}

suspend fun HttpClient.download(
    url: String,
    requestHeaders: List<Pair<String, String>>,
): ByteString {
  return request(url){
        headers{
            for ((name, value) in requestHeaders) {
                append(name, value)
            }
        }
    }.bodyAsBytes().toByteString()
}

@Suppress("SuspiciousIndentation")
suspend fun HttpClient.downloadRage(
    url: String,
    outFile: File,
    chunkSize: Int = 1024
) {
    val length = head(url).headers[HttpHeaders.ContentLength]?.toLong() as Long
    val lastByte = length - 1

    var start = outFile.size
    val sink = outFile.writeSink()

        try {
            while (true) {
                val end = min(start + chunkSize - 1, lastByte)
                val data = get(url) {
                    header("Range", "bytes=${start}-${end}")
                }.body<ByteArray>()
                sink.write(data)
                if (end >= lastByte) break
                start += chunkSize
            }
        }finally {
            sink.close()
        }
}

suspend fun HttpClient.downloadStream(url: String,
                                      outFile: File,
                                      chunkSize: Long = 1024,
                                      requestHeaders: List<Pair<String, String>> = emptyList(),
                                      pairRange: LongRange?=null,
                                      downloadListener: ProgressListener?=null){
    prepareGet(url){
        requestHeaders.isNotEmpty().ifTrue {
            headers{
                for ((name, value) in requestHeaders) {
                    append(name, value)
                }
            }
        }
        pairRange?.let { header("Range", "bytes=${it.first}-${it.last}") }
        downloadListener?.let { onDownload(it) }
    }.execute { httpResponse ->
        val channel: ByteReadChannel = httpResponse.body()
        val sink = outFile.writeSink()
        while (!channel.isClosedForRead) {
            val packet = channel.readRemaining(chunkSize)
            while (!packet.exhausted()) {
                //val bytes = packet.readBytes()
                sink.writePacket(packet)
                println("Received ${outFile.size} bytes from ${httpResponse.contentLength()}")
            }
        }
        println("A file saved to ${outFile.path}")
    }
}

suspend fun HttpClient.getFileSize(url: String): Result<Long> {
    try {
        val response = head(url)
        val fileSize = response.headers["Content-Length"]?.toLong() ?: 0
        return Result.success(fileSize)
    } catch (e: Exception) {
        Timber.e(e)
        return Result.failure(e)
    }
}

suspend fun HttpClient.getByteChannel(url: String, offset: Long): Result<ByteReadChannel> {
    try {
        val response = prepareGet(url) {
            headers {
                if (offset > 0) {
                    append("Range", "bytes=$offset-")
                }
            }
        }
        return Result.success(response.body())
    } catch (e: Exception) {
        return Result.failure(e)//Failure(KError("download_failed", e.message ?: "Failed to make request", ""))
    }
}


/**
 * Performs an HTTP GET request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the GET request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.getResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { get(urlString, block).body<T>() }

/**
 * Performs an HTTP POST request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the POST request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.postResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { post(urlString, block).body() }

/**
 * Performs an HTTP PUT request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the PUT request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.putResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { put(urlString, block).body() }

/**
 * Performs an HTTP DELETE request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the DELETE request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.deleteResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { delete(urlString, block).body() }

/**
 * Performs an HTTP PATCH request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the PATCH request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.patchResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { patch(urlString, block).body() }

/**
 * Performs an HTTP HEAD request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the HEAD request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.headResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { head(urlString, block).body() }

/**
 * Performs an HTTP OPTIONS request synchronously and returns the result as a [Result] of type [T].
 *
 * @param urlString The URL for the OPTIONS request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return Result<T> representing the synchronous operation.
 */
suspend inline fun <reified T> HttpClient.optionsResult(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Result<T> = runSafeSuspendCatching { options(urlString, block).body() }


/**
 * Runs a suspending function [block] safely, catching any exceptions that occur during its execution.
 * Returns a [Result] indicating success or failure of the function.
 *
 * @param block the suspending function to be executed safely
 * @return a [Result] indicating success ([Result.success]) or failure ([Result.failure]) of the function
 * @throws CancellationException if the coroutine is cancelled during the execution of [block]
 */
inline fun <R> runSafeSuspendCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (c: CancellationException) {
        throw c
    } catch (e: ResponseException) {
        Result.failure(e)
    } catch (t: Throwable) {
        Result.failure(t)
    }
}


/**
 * Executes an asynchronous HTTP GET request using the provided URL.
 *
 * @param urlString The URL for the GET request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
 suspend inline fun <reified T> HttpClient.getResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { get(urlString, block).body() } }
    }

/**
 * Executes an asynchronous HTTP POST request using the provided URL.
 *
 * @param urlString The URL for the POST request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
 suspend inline fun <reified T> HttpClient.postResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { post(urlString, block).body() } }
    }

/**
 * Executes an asynchronous HTTP PUT request using the provided URL.
 *
 * @param urlString The URL for the PUT request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
 suspend inline fun <reified T> HttpClient.putResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { put(urlString, block).body() } }
    }

/**
 * Executes an asynchronous HTTP DELETE request using the provided URL.
 *
 * @param urlString The URL for the DELETE request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
suspend inline fun <reified T> HttpClient.deleteResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { delete(urlString, block).body() } }
    }

/**
 * Executes an asynchronous HTTP PATCH request using the provided URL.
 *
 * @param urlString The URL for the PATCH request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
 suspend inline fun <reified T> HttpClient.patchResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { patch(urlString, block).body() } }
    }

/**
 * Executes an asynchronous HTTP HEAD request using the provided URL.
 *
 * @param urlString The URL for the HEAD request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
 suspend inline fun <reified T> HttpClient.headResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { head(urlString, block).body() } }
    }

/**
 * Executes an asynchronous HTTP OPTIONS request using the provided URL.
 *
 * @param urlString The URL for the OPTIONS request.
 * @param block Optional, allows customization of the request using HttpRequestBuilder.
 * @return A Deferred<Result<T>> representing the asynchronous operation.
 */
 suspend inline fun <reified T> HttpClient.optionsResultAsync(
    urlString: String,
    noinline block: HttpRequestBuilder.() -> Unit = {},
): Deferred<Result<T>> =
    coroutineScope {
        async { runSafeSuspendCatching { options(urlString, block).body() } }
    }
